﻿using Nemerle.Assertions;
using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.Linq;
using Nemerle2.Compiler;
using System.IO;
using System.Xml.Linq;
using Nemerle.Compiler;
using Nemerle.Xml;

namespace DocBrowser
{
  /// <summary>
  /// Description of Assemblies.
  /// </summary>
  partial class Host
  {
    _metadataProvider : IMetadataProvider  = MetadataReader.CCI.CciMetadataProvider();
    _assemblies       : List[IReferencedAssembly] = List();
    _docs             : XmlDocRepository = XmlDocRepository();
    _operatorInfos    : Hashtable[string * bool, int * int] = Hashtable(); // [fullName, isUnary * leftBindingPower * rightBindingPower]
    
    public OperatorInfos[fullName : string,  isUnary : bool] : option[int * int]
    {
      get
      {
        mutable result;
        
        if (_operatorInfos.TryGetValue((fullName, isUnary), out result))
          Some(result)
        else
          None()
      }
    }
    
    public LoadAssembly([NotNull] path : string) : IReferencedAssembly
    {
      def assm = _metadataProvider.LoadAssembly(path);
      _assemblies.Add(assm);
      
      foreach (attr when attr.FullName	== "Nemerle.Internal.OperatorAttribute" in assm.CustomAttributes.CustomAttributes)
      {
        def fullName = $"$(attr.ArgumentValue[0]).$(attr.ArgumentValue[1])";
        def isUnary = (attr.ArgumentValue[2] :> CustomAttributeValue.Bool).Value;
        def leftBindingPower = (attr.ArgumentValue[3] :> CustomAttributeValue.Int).Value;
        def rightBindingPower = (attr.ArgumentValue[4] :> CustomAttributeValue.Int).Value;
        //Diagnostics.Debug.WriteLine($"'$fullName', $isUnary, $leftBindingPower, $rightBindingPower");
        _operatorInfos[fullName, isUnary] = (leftBindingPower, rightBindingPower);
      }
      
      def xmlDoc = Path.ChangeExtension(path, ".xml");
      when (File.Exists(xmlDoc))
        _docs.LoadFile(xmlDoc);

      assm
    }
    
    public Docs : XmlDocRepository
    {
      get { _docs };
    }

   public MacroInfoToHtml(macroInfo : IMacroInfo) : XElement
   {
      def fullName = $<#..$(macroInfo.Namespace; ".").$(macroInfo.Name)#>;
      def paramsCount = macroInfo.Parameters.Length;
      def isOperator() : bool
      {
        if (paramsCount == 0 || paramsCount > 2)
          false
        else if (LexerBase.IsOperatorChar(macroInfo.Name[0]))
          true
        else
          this.OperatorInfos[fullName, paramsCount == 1].IsSome
      }
      def getBindingPower() : int * int
      {
        assert2(isOperator());
        match (this.OperatorInfos[fullName, paramsCount == 1])
        {
          | Some(result) => result
          | _            => (200, 201)
        }
      }
      def parameters = macroInfo.Parameters;
      def paramsCount = parameters.Length;
      def nodes = List();
            
      if (isOperator())
      {
        def (leftBindingPower, rightBindingPower) = getBindingPower();
        def operKind = if (paramsCount == 1) "Unary" else "Binary";
        nodes.Add(xml <# <h3>$operKind macro operator: <b>$(macroInfo.Name)</b></> #>);
                
        if (paramsCount == 1)
          nodes.Add(xml <#<p>Syntax: <pre class="code"><spen class="STRING">"$(macroInfo.Name)"</spen> $(parameters.Head.Name)</pre></p>#>);
        else
          nodes.Add(xml <#<p>Syntax: <pre class="code">$(parameters.Head.Name) <spen class="STRING">"$(macroInfo.Name)"</spen> $(parameters.Tail.Head.Name)</pre><br/>
                           Left binding power: <b>$leftBindingPower</b><br/>
                           Right binding power: <b>$rightBindingPower</b>
                          </p>#>);
        nodes.Add(xml <#<p/>#>);
      }
      else
      {
        nodes.Add(xml <#<h3><b>$(macroInfo.Name)</b> Macro</>#>);
                
        def appliedTo = macroInfo.AppliedTo;
        if (appliedTo == 0)
          nodes.Add(xml <#<p>Applied to: expression</>#>);
        else
          nodes.Add(xml <#<p>Applied to: $(appliedTo.ToString().ToLower())</>#>);
          
        if (macroInfo.Description.Length > 0)
          nodes.Add(xml <#<p>Syntax: <pre class="code">$(macroInfo.Description)</></>#>);
        else
          nodes.Add(xml <#<p><pre class="code">$(macroInfo.Name)(..$(macroInfo.Parameters))</></>#>);
                
      }
            
      def elem = ret : {
          foreach (type in macroInfo.TypeInfos)
            when (this.Docs.GetDoc(type) is Some(elem))
              ret(elem);
          xml <# <p/> #>
        };

      when (paramsCount > 0)
      {
        def makeParam(p : MacroParameterInfo) : XElement
        {
          def param = elem.Elements("param")?.FirstOrDefault(e => e.Attribute("name")?.Value == p.Name);
          def paramDec = param?.Elements("para")?.Select(p => xml <#<p>..$(p.Nodes())</p>#>) ?? [];
          
          xml <# <span><pre>$p</pre>..$paramDec</> #>
        }
        nodes.Add(xml <#<p><b>where</b><br/>
          <ul>
            <li $foreach (p in parameters)>$(makeParam(p))</li>
          </ul>
        </p>#>);
      }
            
      def summary = elem.Element("summary");
            
      when (summary != null)
      {
        foreach (p when p != null in summary.Elements("para")) //?.Select(p => $"<p>$(p.Value)</p>")
          nodes.Add(xml <# <p>..$(p.Nodes())</>#>);
      }

      def xs = elem.Element("remarks");
      def remarks = (xs?.Elements("para")?.Elements("remark")?.Select(r => xml <#<p>$(r.Nodes())</p>#>) ?? []).ToArray();
      
      when (remarks.Length > 0)
        nodes.Add(xml <# <span><h3>Remarks</h3>
            ..$remarks
          </span>#>);
                
      xml <# <span>..$nodes</>#>
    }
  }
}
